
/**
 * @name MassMF1 (MASS Moisture Flux 1)
 * Calculates surface moisture flux
 * for January 2-31, 1980 
 * Part of Pacific Northwest Climate Data Analysis
 *
 * @version 0.5.401 Beta, MASS Moisture Flux with prototyping, 1st commit
 * @date 10/27/2013
 * @author Niko Simonson
 * @author Brett Yasutake
 */

// IMPORTS
import MASS.*;                  // MASS Library
import java.io.IOException;     // IO Exceptions
import java.util.*;             // Vectors, etc.
import java.util.AbstractMap.*; // for simple key-value pairs
import ucar.ma2.ArrayFloat;     // for netCDF reader
import ucar.ma2.IndexIterator;  // for netCDF reader
import ucar.nc2.NetcdfFile;     // for netCDF reader
import ucar.nc2.Variable;       // for netCDF reader

public class MassMF1 extends Place {

    // CONSTANTS - MASS arguments
    private static final String[] MASSARGS = {"dslab", "dslab-302",
        "machinefile.txt", "12345"};            // connection arguments
    private static final int NPROCESSES = 1;    // number of processes to employ
    private static final int NTHREADS = 8;      // number of threads to employ
    private static final int HANDLE = 0;        // unique Places handle number
    
    // CONSTANTS - Functions
    private static final int init_ = 0;           // initialize Places
    private static final int computeMF_ = 1;      // compute regional avg temps
    private static final int storeMFResults_ = 2; // exchange regional temps
    
    // CONSTANTS - Northwest regional climate data sample
    private static final String FILENAMEBEGIN = "/home/brett/netcdf_files/1980_jan/wrfout_d02_1980-01-";
    private static final String FILENAMEEND = ":00:00"; // end of file name
    private static final int DAYS = 30;                 // days of data collection
    private static final int DAYSEGMENTS = 4;           // time slices per day
    private static final int XRANGE = 123;              // data width (# of grid cells)
    private static final int YRANGE = 162;              // data height (# of grid cells)
    
    // CONSTANTS - Target variables
    private static final String XWIND = "U10";      // east-west windspeed at 10m above surface
    private static final String YWIND = "V10";      // north-south windspeed at 10m above surface
    private static final String MOISTURE = "Q2";    // moisture at 2m above surface
    
    // VARIABLES
    private int myX;         // computing node's X coordinate
    private int myY;         // computing node's Y coordinate
    private double[][] mf;   // moisture flux scalar
    private double[][] dir;  // moisture flux direction in radians

    private static ResultsAdapter[][] ra;   // stores results
    
    private static Places computeMoistureFlux = null;  // parallel encironment
    
    private static String[][] resultsName;  // results files' name
    private static SimpleEntry<String, Integer[]> entryMagnitude;
    private static SimpleEntry<String, Integer[]> entryDirection;

    /**
     * @name MasMF1
     * @param interval unused Empty constructor required for MASS Place
     * implementation
     */
    public MassMF1(Object interval) {
        // A constructor implementation is required for MASS Place
        mf = new double[XRANGE][YRANGE];
        dir = new double[XRANGE][YRANGE];
    }

    /**
     * @name callMethod Provides enumerated list of functions to be parallelized
     * in MASS
     *
     * @param funcId - function ID (listed as Constants)
     * @param args - function parameters
     * @return - unused
     */
    public Object callMethod(int funcId, Object args) {
        switch (funcId) {
            case init_:
                return init(args);
            case computeMF_:
                return computeMF(args);
            case storeMFResults_:
                return storeMFResults(args);
        }
        return null;
    }

    /**
     * @name init Initialization information for MASS Places index[0]: days
     * index[1]: time slices in a day
     *
     * @param args MASS initialization arguments, # of processes, # of threads
     * @return unused
     */
    public Object init(Object args) {  
        
        // Each place keeps track of its time position
        myX = index[1];
        myY = index[0];

        // Name the results files
        resultsName[myX][myY] = "MoistureFlux" + myY + "-" + myX;
        
        // Instantiate the results adapters
        ra[myX][myY] = new ResultsAdapter(ResultsAdapter.DOUBLE, resultsName[myX][myY], entryMagnitude, entryDirection);

        return (Object)null;
    }

    /**
     * @name computeMF
     * Open netCDF file and compute moisture flux over
     * the entire region
     *
     * @param args unused
     * @return true = success, false = exception occurred
     */
    public Object computeMF(Object args) {
//TODO generalize this code into an input adapter

        // VARIABLES
        NetcdfFile inputFile = null;    // target netCDF file
        String fileName;                // file name
        Variable xSpd, ySpd, qv;        // E-W, N-S wind speed NetCDF variables
        ArrayFloat.D3 d3x, d3y, d3q;    // 3D NetCDF float array
        double xW, yW, q;

        try {
            // open target netCDF file
            fileName = getFileName(myX, myY);
            inputFile = NetcdfFile.open(fileName, null);	// Open the file

            // Find target variable
            xSpd = inputFile.findVariable(XWIND);
            ySpd = inputFile.findVariable(YWIND);
            qv = inputFile.findVariable(MOISTURE);
            
            // Get list of file's variables
            List<Variable> stubbyVars = inputFile.getVariables();
            xSpd = stubbyVars.get(stubbyVars.indexOf(xSpd));
            ySpd = stubbyVars.get(stubbyVars.indexOf(ySpd));
            qv = stubbyVars.get(stubbyVars.indexOf(qv));

            // Read values from variable (temperatures over the region)
            d3x = (ArrayFloat.D3) xSpd.read();
            d3y = (ArrayFloat.D3) ySpd.read();
            d3q = (ArrayFloat.D3) qv.read();
            
            // iterators
            IndexIterator iterX, iterY, iterQ;

            int x = 0;
            int y = 0;

            iterX = d3x.getIndexIterator();
            iterY = d3y.getIndexIterator();
            iterQ = d3q.getIndexIterator();
            
            // Fill arrays with values from file
            while (iterX.hasNext()) {
                iterX.next();
                iterY.next();
                iterQ.next();
                
                xW = (double)iterX.getFloatCurrent();
                yW = (double)iterY.getFloatCurrent();
                q = (double)iterQ.getFloatCurrent();
                
                mf[x][y] = q * Math.sqrt(Math.pow(xW, 2.0) + Math.pow(yW, 2.0));
                if (yW != 0) {
                    dir[x][y] = Math.atan2(xW, yW);
                }
                else if (xW > 0) {
                    dir[x][y] = 0.0;
                }
                else if (xW < 0) {
                    dir[x][y] = Math.PI;
                }
                else {
                    // no wind, so no moisture flux
                    dir[x][y] = 0.0;
                    mf[x][y] = 0.0;
                }
                // iterating through 2 dimensions
                if (y == YRANGE - 1) {
                    x++;
                    y = 0;
                } else {
                    y++;
                }
            }
            return (Object) true;
        } catch (java.io.IOException e) {
            e.printStackTrace();
            return (Object) false;
        } catch (Exception e) {
            e.printStackTrace();
            return (Object) false;
        } finally {		// Close out the file no matter what occurs
            if (inputFile != null) {
                try {
                    inputFile.close();
                } catch (IOException ioe) {
                    ioe.printStackTrace();
                }
            }
            return (Object) null;
        }
    }

    /**
     * @name getFileName Private helper function to dynamically generate correct
     * file name
     *
     * @param thisX daily time slice
     * @param thisY day
     *
     * @return
     */
    private String getFileName(int thisX, int thisY) {
        // VARIABLES
        String strX, strY;  // file component numbers

        // calculate correct day of the month
        strY = Integer.toString(thisY + 2);

        // calculate correct time of the day
        strX = Integer.toString(thisX * 6);

        // adjust for numbers less than 10
        if (Integer.parseInt(strX) < 10) {
            strX = "0" + strX;
        }
        if (Integer.parseInt(strY) < 10) {
            strY = "0" + strY;
        }
        // return proper file name
        return FILENAMEBEGIN + strY + "_" + strX + FILENAMEEND;
    }
    
    /**
     * @name storeMFResults Record results to the results adapter
     * @param args
     * @return unused
     */
    public Object storeMFResults(Object args) {
        ra[myX][myY].log2DVar("magnitude", mf);
        ra[myX][myY].log2DVar("direction", dir);
        
        return null;
    }

    /**
     * @name runAnalysis Performs entire analysis, manages MASS
     *
     * @param stat StatusModel UI view
     * @param initialized set to true if MASS has already been initialized
     * @throws Exception
     */
    public static void runAnalysis(StatusModel stat, boolean initialized) throws Exception {
        
        // VARIABLES
        long start, end, totalTime; // data provenance for execution time
        
        // regional temperature calculations
        int count = DAYS * DAYSEGMENTS;     // total number of files
        
        // dummy object to prevent array over- or underflow
        Object[] fixer = new Object[count];
        StatusModel sm = stat;
        
        // Initialization setup
        if (!initialized) {
            ra = new ResultsAdapter[DAYSEGMENTS][DAYS];
            resultsName = new String[DAYSEGMENTS][DAYS];
        
            Integer[] dimensions = new Integer[2];
            dimensions[0] = XRANGE;
            dimensions[1] = YRANGE;
               
            entryMagnitude = new SimpleEntry<String, Integer[]>("magnitude", dimensions);
            entryDirection = new SimpleEntry<String, Integer[]>("direction", dimensions);
        }
       try {
            sm.reportMessage("Starting MASS");
            // begin time measurement
            start = new Date().getTime();

            // initialize MASS
            if (!initialized) MASS.init(MASSARGS, NPROCESSES, NTHREADS);

            // Create Places
            // Each computing node corresponds to one netCDF file         
            if (!initialized) 
                computeMoistureFlux = new Places(HANDLE, "MassMF1", null,
                    DAYS, DAYSEGMENTS);  
            sm.reportMessage("Computational Nodes created");

            // Initialize Places
            if (!initialized) computeMoistureFlux.callAll(init_, fixer);
            sm.reportMessage("Computational Nodes initialized");

            // Compute moisture flux
            computeMoistureFlux.callAll(computeMF_, fixer);
            sm.reportMessage("Calculations completed");

            computeMoistureFlux.callAll(storeMFResults_, fixer);

            end = new Date().getTime();
            totalTime = end - start;    //milliseconds
            
            sm.reportMessage("Results stored");

            System.err.println("Total execution time (milliseconds): "
                    + totalTime);
        }
        catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    /**
     * @name finish Terminate and clean-up MASS by calling MASS's finish
     * function
     */
    public static void finish() {
        MASS.finish();
    }
} // end MASS Heatwave
